Introducción al manejo de datos con R

Comentarios iniciales y objetivos

En este documento se pretende hacer un repaso rápido de algunas de las posibilidades disponibles para el manejo de conjuntos de datos en R, con el objetivo fundamental de saber cómo gestionar la información contenida en los datos para extraer solamente la parte que resulte de interés en cada aplicación.

Análisis inicial de un conjunto de datos

Como data scientists modelizadores nos llegarán datos de muy diferente índole, posiblemente en distintos formatos y algunas veces muy “sucios”. Nuestro trabajo es resumir que demonios hay en el conjunto de datos y qué información interesante podríamos utilizar para los objetivos concretos del proyecto de modelización.

En primer lugar, necesitamos tener una idea de tamaño y disposición del archivo. Cuantas variables y registros tenemos, sus tipos, si todo parece correcto en cuanto a codificación o se ven cosas raras, esto a un nivel general. En particular, cada data set es un mundo y nos suscitará distintas cuestiones que debemos saber (auto)resolver para no quedarnos con dudas y tener el mayor conocimiento sobre los datos antes de modelar " a lo loco".

En este ejemplo, nos llega un dataset en formato RDS (formato comprimido de R para guardar objetos de todo tipo). En primer lugar, saber como leerlo! La función adecuada en este caso es readRDS(‘ruta al archivo/archivoRDS.RDS’). Como nos han dicho que el proyecto trata de consumo de medios, llamaremos al archivo datosMedia.

Visualización de los datos en bruto

# Leemos los datos desde formato binario comprimido RDS (la mejor opción en tiempo y espacio para guardar cualquier tipo de objeto de R!)
datosMedia <- readRDS('F:/Documentos/Master Comercio 2021-22/Datos/datos_usoMedia.RDS')

# Salida típica de data.frame. Pinta todo hasta un cierto límite de filas suele ser peor para visualizar...por eso le pido que nos enseñe solamente head()
head(data.frame(datosMedia)) 
##         date IdPanelist         Medio    DiaSem DayParting Plat   Grupo Week
## 1 2021-09-01      41240    TV_Antena3 miércoles     Mañana   TV A3Media    1
## 2 2021-09-01      42501  App_Facebook miércoles      Tarde  App    RRSS    1
## 3 2021-09-01      73126 App_Instagram miércoles   Mediodia  App    RRSS    1
## 4 2021-09-01      56200 App_Instagram miércoles   Mediodia  App    RRSS    1
## 5 2021-09-01      22850   Web_youtube miércoles      Tarde  Web Youtube    1
## 6 2021-09-01      66366 App_Instagram miércoles   Mediodia  App    RRSS    1
##   hora    Sex AgeGroup AdultsWithKids   RCH
## 1    9 Female  25 - 34          FALSE  TRUE
## 2   18   Male     45 +           TRUE  TRUE
## 3   16 Female  18 - 24           TRUE  TRUE
## 4   14 Female  18 - 24          FALSE FALSE
## 5   19 Female  25 - 34          FALSE FALSE
## 6   15   Male  25 - 34           TRUE  TRUE
# Salida típica de un tibble de dplyr (siempre corta la salid por filas y columnas y es cómodo para visualizar)
tibble(datosMedia)
## # A tibble: 704,806 x 13
##    date       IdPanelist Medio   DiaSem DayParting Plat  Grupo  Week  hora Sex  
##    <date>          <dbl> <chr>   <ord>  <fct>      <chr> <chr> <int> <dbl> <fct>
##  1 2021-09-01      41240 TV_Ant~ miérc~ Mañana     TV    A3Me~     1     9 Fema~
##  2 2021-09-01      42501 App_Fa~ miérc~ Tarde      App   RRSS      1    18 Male 
##  3 2021-09-01      73126 App_In~ miérc~ Mediodia   App   RRSS      1    16 Fema~
##  4 2021-09-01      56200 App_In~ miérc~ Mediodia   App   RRSS      1    14 Fema~
##  5 2021-09-01      22850 Web_yo~ miérc~ Tarde      Web   Yout~     1    19 Fema~
##  6 2021-09-01      66366 App_In~ miérc~ Mediodia   App   RRSS      1    15 Male 
##  7 2021-09-01      86041 App_Tw~ miérc~ Tarde      App   RRSS      1    17 Male 
##  8 2021-09-01       7112 TV_Ant~ miérc~ Madrugada  TV    A3Me~     1     1 Male 
##  9 2021-09-01      79769 App_Fa~ miérc~ Mañana     App   RRSS      1    13 Fema~
## 10 2021-09-01      85549 App_Yo~ miérc~ Mañana     App   Yout~     1    13 Fema~
## # ... with 704,796 more rows, and 3 more variables: AgeGroup <fct>,
## #   AdultsWithKids <fct>, RCH <fct>
# Salida típica de un data.table (enseña principio y final del archivo, lo cual es útil!)
data.table(datosMedia)
##               date IdPanelist         Medio    DiaSem DayParting Plat    Grupo
##      1: 2021-09-01      41240    TV_Antena3 miércoles     Mañana   TV  A3Media
##      2: 2021-09-01      42501  App_Facebook miércoles      Tarde  App     RRSS
##      3: 2021-09-01      73126 App_Instagram miércoles   Mediodia  App     RRSS
##      4: 2021-09-01      56200 App_Instagram miércoles   Mediodia  App     RRSS
##      5: 2021-09-01      22850   Web_youtube miércoles      Tarde  Web  Youtube
##     ---                                                                       
## 704802: 2021-09-30      77536 App_Instagram    jueves  Primetime  App     RRSS
## 704803: 2021-09-30      29602  App_Facebook    jueves      Tarde  App     RRSS
## 704804: 2021-09-30       4496   Web_twitter    jueves   Mediodia  Web     RRSS
## 704805: 2021-09-30      75216  TV_Telecinco    jueves  Primetime   TV Mediaset
## 704806: 2021-09-30      75911    App_MiTele    jueves   Mediodia  App Mediaset
##         Week hora    Sex AgeGroup AdultsWithKids   RCH
##      1:    1    9 Female  25 - 34          FALSE  TRUE
##      2:    1   18   Male     45 +           TRUE  TRUE
##      3:    1   16 Female  18 - 24           TRUE  TRUE
##      4:    1   14 Female  18 - 24          FALSE FALSE
##      5:    1   19 Female  25 - 34          FALSE FALSE
##     ---                                               
## 704802:    5   23   Male  18 - 24          FALSE FALSE
## 704803:    5   16   Male  18 - 24          FALSE  TRUE
## 704804:    5   14 Female     45 +           TRUE  TRUE
## 704805:    5   23 Female  25 - 34          FALSE  TRUE
## 704806:    5   13   Male  18 - 24          FALSE FALSE
# Salida en formato tabal dinámica para nuestro documento html. Súper útil para visualización de tablas! 
datatable(head(datosMedia,1000), options = list(autoWidth = TRUE, scrollX = TRUE),
              caption = 'Datos Uso de Medios',
              class = 'cell-border stripe nowrap')

Tenemos ante nuestros ojos los datos de utilización de distintos medios de TV, Radio, Apps y Webs de cierto panel de individuos. Interesante, queremos saber cosas pero ya!

Conclusiones del primer vistazo

De momento deducimos:

  • El tamaño del archivo es de 704806 filas o registros y 13 columnas o variables. Archivo no excesivamente grande pero ya de tamaño considerable para según que cosas.

  • Las filas parecen identificar movimientos de usuarios de distintos medios y en diferentes momentos del tiempo, parece un conjunto dinámico…pero luego parece que tenemos información sobre los propios individuos o panelistas y eso es información estática. Con lo cual estamos ante un conjunto de carácter híbrido ya que contiene parte de evolución y parte de caracterización.

  • Para el análisis dinámico es relevante plantear cuestiones como la granularidad de los datos, que sería la unidad mínima de cambio de tiempo. Si prestamos atención a las columnas del archivo, descubrimos que tenemos la fecha (date) cuya unidad mínima de media es el día, entonces, ¿son datos diarios?. La respuesta es no! La fecha se mide en días pero no podemos asegurar que sean datos diarios ya que tenemos otras variables como dayParting (franja horaria) y hora!! (también tenemos semana y día de la semana pero con granularidad mayor o igual que la propia fecha así que nada…) Conclusión, podemos afirmar que tenemos datos de granularidad horaria! Bien!

  • Otro aspecto importante a tener en cuenta es el periodo de tiempo de los datos disponibles. En la salida de data.table podemos ver que la fecha de las últimas filas es 2021-09-30, si asumimos que los datos están ordenados por fecha, tenemos un mes de datos. Pero esto es arriesgado afirmarlo…no lo sabemos. Podemos pedir un summary() para ver el rango de variación de la variable fecha (y de todas las variables si queremos). Tenemos la opción del slice_max

# Podemos pedir un cortecito por el valor más alto de fecha ()
datosMedia %>% slice_max(date)
## # A tibble: 23,900 x 13
##    date       IdPanelist Medio   DiaSem DayParting Plat  Grupo  Week  hora Sex  
##    <date>          <dbl> <chr>   <ord>  <fct>      <chr> <chr> <int> <dbl> <fct>
##  1 2021-09-30      19554 Web_tw~ jueves Primetime  Web   RRSS      5    21 Fema~
##  2 2021-09-30      58839 App_In~ jueves Mediodia   App   RRSS      5    16 Male 
##  3 2021-09-30       1722 Web_tw~ jueves Mañana     Web   RRSS      5     9 Fema~
##  4 2021-09-30      57931 App_In~ jueves Mediodia   App   RRSS      5    15 Fema~
##  5 2021-09-30      82179 App_In~ jueves Mediodia   App   RRSS      5    16 Fema~
##  6 2021-09-30      53678 Web_yo~ jueves Mañana     Web   Yout~     5    12 Fema~
##  7 2021-09-30      19581 App_Fa~ jueves Primetime  App   RRSS      5    20 Fema~
##  8 2021-09-30      34134 App_In~ jueves Resto      App   RRSS      5     4 Male 
##  9 2021-09-30      33419 Web_yo~ jueves Mañana     Web   Yout~     5    10 Fema~
## 10 2021-09-30      21119 Web_yo~ jueves Mañana     Web   Yout~     5     9 Male 
## # ... with 23,890 more rows, and 3 more variables: AgeGroup <fct>,
## #   AdultsWithKids <fct>, RCH <fct>
# O también podemos pedir el corte mínimo
#datosMedia %>% slice_min(date)

# Summary para ver un poco la distribución de las variables
summary(datosMedia$date)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2021-09-01" "2021-09-08" "2021-09-16" "2021-09-15" "2021-09-23" "2021-09-30"

Finalmente, habíamos supuesto bien y el dataset está ordenado por fecha, desde el 1 hasta el 30 de septiembre. Otra conclusiones son: 1) que hay en torno a 24 mil registros en los días 1 y 30. 2) que la distribución de la fecha es bastante uniforme, media y mediana en torno a mitad de mes, el tercer cuartil el día 23…pues parece que tenemos similar cantidad de registros por día.

Distintas posibilidades de programación en R

Nos planteamos ahora cuántos registros por hora tenemos. Para ello tenemos que contar, y esto es un paso muy importante a la hora de resumir y entender datos. En el formato de las clásicas queries de sql para consulta de bases de datos, tenemos un sinfín de posibilidades con R base, dplyr o data.table. La sintaxis, finalmente, es a gusto del consumidor pero hay ciertas generalidades que podemos tener en cuenta a la hora de elegir nuestra aproximación:

  • R base: es el tipo de programación primigenio de R, generalmente no se producen cambios que nos obliguen a modificar nuestro código como sucede con algunos paquetes. La sintaxis suele ser un poco menos legible y, en ocasiones puede resultar confusa por la estructura “anidada” de las consultas.

  • dplyr: muy buena opción para códigos estructurados y legibles a simple vista. en cuanto a tiempos, se comporta generalmente bien aunque las consultas u operaciones complejas con un volumen relativamente alto de datos pueden tardar bastante…mi preferido en cuanto a sintaxis.

  • data.table: opción favorita para amantes de la velocidad de proceso! Famoso por su rapidez en el manejo de grandes volúmenes de datos. La sintaxis es más parecida a a R base con estructura tipo datos[filas,columnas] pero en data.table se añade un tercer argumento generalmente reservado para agrupaciones con by=, esto da mucha flexibilidad para la programación.

Cualquier aproximación de código que encontremos por ahí es válida mientras cumpla el objetivo que nos marcamos. Generalmente mi opción es usar lo que me resulta más fácil en cada momento pero con cierta componente tendente a la optimización de tiempos… Así, algo que me resulta muy útil en el día a día (como asiduo usuario de dplyr) es la utilización del maravilloso paquete dtplyr que es capaz de traducir código en dplyr a código en data.table mediante la función show_query(). Muy recomendable cuando dplyr se queda lento.

# Utilizo dtplyr para traducir el slice_min que tanto tardaba. 
lazy_dt(datosMedia) %>% slice_min(date) %>% show_query()
## `_DT1`[, .SD[order(date)][frankv(date, ties.method = "min", na.last = "keep") <= 
##     1L]]
# Aprovecho la query que me devuelve
data.table(datosMedia)[, .SD[order(date)][frankv(date, ties.method = "min", 
    na.last = "keep") <= 1L]]
##              date IdPanelist         Medio    DiaSem DayParting Plat   Grupo
##     1: 2021-09-01      41240    TV_Antena3 miércoles     Mañana   TV A3Media
##     2: 2021-09-01      42501  App_Facebook miércoles      Tarde  App    RRSS
##     3: 2021-09-01      73126 App_Instagram miércoles   Mediodia  App    RRSS
##     4: 2021-09-01      56200 App_Instagram miércoles   Mediodia  App    RRSS
##     5: 2021-09-01      22850   Web_youtube miércoles      Tarde  Web Youtube
##    ---                                                                      
## 24560: 2021-09-01      21007    TV_Antena3 miércoles  Primetime   TV A3Media
## 24561: 2021-09-01      65354    Web_tiktok miércoles     Mañana  Web    RRSS
## 24562: 2021-09-01       2288 Web_instagram miércoles   Mediodia  Web    RRSS
## 24563: 2021-09-01      32774   Web_twitter miércoles      Tarde  Web    RRSS
## 24564: 2021-09-01      89904   Web_twitter miércoles     Mañana  Web    RRSS
##        Week hora    Sex AgeGroup AdultsWithKids   RCH
##     1:    1    9 Female  25 - 34          FALSE  TRUE
##     2:    1   18   Male     45 +           TRUE  TRUE
##     3:    1   16 Female  18 - 24           TRUE  TRUE
##     4:    1   14 Female  18 - 24          FALSE FALSE
##     5:    1   19 Female  25 - 34          FALSE FALSE
##    ---                                               
## 24560:    1   21   Male     45 +           TRUE  TRUE
## 24561:    1   12   Male  18 - 24           TRUE  TRUE
## 24562:    1   16 Female     45 +           TRUE  TRUE
## 24563:    1   16 Female  18 - 24          FALSE FALSE
## 24564:    1   11 Female  18 - 24          FALSE FALSE
# Podemos comparar los tiempos con un microbenchmark

# microbenchmark::microbenchmark(
# 
#   dplyr = datosMedia %>% slice_min(date),
#   data.table = data.table(datosMedia)[, .SD[order(date)][frankv(date, ties.method = "min",
#     na.last = "keep") <= 1L]],
#   times = 3
# )

La diferencia es abismal… no es normal el comportamiento tan lento de slice_min. Para estas cosas nos viene genial la librería dtplyr, muy recomendable!

Nota: Recordad comentar (marcar y crtl+Mayus+c) la parte del benchmark a la hora de ejecutar el RMD porque tarda un rato!!

Contar, contar y contar

Como habíamos mencionado, saber contar bien lo que necesitamos es fundamental y nos ahorrará más de un apuro. Vamos a ir haciendo posible preguntas sobre el comportamiento de la gente e intentaremos darles respuesta con consultas sobre el dataset.

  1. ¿Hay duplicados? Cuantos registros distintos tenemos.

En este caso los duplicados serían mismo panelista, en el mismo medio, a la misma hora del mismo día puede aparecer varias veces. Vamos a comprobar.

# Datos distintos
datosMedia %>% distinct()
## # A tibble: 301,360 x 13
##    date       IdPanelist Medio   DiaSem DayParting Plat  Grupo  Week  hora Sex  
##    <date>          <dbl> <chr>   <ord>  <fct>      <chr> <chr> <int> <dbl> <fct>
##  1 2021-09-01      41240 TV_Ant~ miérc~ Mañana     TV    A3Me~     1     9 Fema~
##  2 2021-09-01      42501 App_Fa~ miérc~ Tarde      App   RRSS      1    18 Male 
##  3 2021-09-01      73126 App_In~ miérc~ Mediodia   App   RRSS      1    16 Fema~
##  4 2021-09-01      56200 App_In~ miérc~ Mediodia   App   RRSS      1    14 Fema~
##  5 2021-09-01      22850 Web_yo~ miérc~ Tarde      Web   Yout~     1    19 Fema~
##  6 2021-09-01      66366 App_In~ miérc~ Mediodia   App   RRSS      1    15 Male 
##  7 2021-09-01      86041 App_Tw~ miérc~ Tarde      App   RRSS      1    17 Male 
##  8 2021-09-01       7112 TV_Ant~ miérc~ Madrugada  TV    A3Me~     1     1 Male 
##  9 2021-09-01      79769 App_Fa~ miérc~ Mañana     App   RRSS      1    13 Fema~
## 10 2021-09-01      85549 App_Yo~ miérc~ Mañana     App   Yout~     1    13 Fema~
## # ... with 301,350 more rows, and 3 more variables: AgeGroup <fct>,
## #   AdultsWithKids <fct>, RCH <fct>

Tenemos 301360 registros únicos panelista-medio-día-hora, con lo que el resto (mas de la mitad) son duplicados. La pregunta siguiente es, esto es normal o tiene sentido? Y en principio no parece descabellado que alguien pueda entrar en facebook más de una vez por hora, no?

Esto nos da pie a generarnos un nuevo conjunto de datos que bien podría valer para predicción. Vamos a desarrollar la idea.

  1. ¿Cuantas visitas por hora hace cada panelista a cada medio?

Crearemos la variable visitasHora para recoger esta información. El dataset resultante tendrá 301360 filas y 14 columnas con lo que resumimos la información de los duplicados en una nueva variable, aligerando mucho el archivo y sin perder información. Ordenamos por valores descendentes de la nueva variable para hacernos una idea del máximo de visitas por hora de la gente.

# Generamos dataset sin duplicados con variable que recoja el número de repeticiones
datosMedia %>% group_by_all() %>% summarise(visitasHora=n()) %>% arrange(desc(visitasHora)) %>% ungroup()->datosMedia_distinct
## `summarise()` has grouped output by 'date', 'IdPanelist', 'Medio', 'DiaSem', 'DayParting', 'Plat', 'Grupo', 'Week', 'hora', 'Sex', 'AgeGroup', 'AdultsWithKids'. You can override using the `.groups` argument.

Pues un récord de 564 visitas a la web de facebook el típico domingo por la tarde… Esta estructura de datos nos permitiría modelizar el número de visitas a la hora en función de perfil sexo-edad, variables temporales y medios plataforma y grupos.

  1. Que perfil tienen los grandes consumidores de medios?

Pediremos que nos muestre el summary de

# Perfil mediante tabla cruzada de panelistas con más de 100 visitas a la hora (independiente del medio que visiten)
datosMedia_distinct %>% filter(visitasHora > 100) %>% 
            select(IdPanelist,Sex,AgeGroup) %>% distinct() %>%
                        tabyl(Sex,AgeGroup) %>% adorn_totals(c("row", "col")) %>%
                                                adorn_percentages("col") %>% 
                                                adorn_pct_formatting(rounding = "half up", digits = 0) %>%
                                                adorn_ns() %>%
                                                adorn_title("combined") %>%
                                                knitr::kable()
Sex/AgeGroup < 18 18 - 24 25 - 34 35 - 44 45 + Total
Female
75% (3) 71% (5) 33% (2) 45% (5) 54% (15)
Male
25% (1) 29% (2) 67% (4) 55% (6) 46% (13)
N.A.
0% (0) 0% (0) 0% (0) 0% (0) 0% (0)
Total
100% (4) 100% (7) 100% (6) 100% (11) 100% (28)
# Consultamos el número de panelisyas de la muestra
n_distinct(datosMedia_distinct$IdPanelist)
## [1] 1524
# Perfil mediante tabla cruzada de panelistas en la base general
datosMedia_distinct  %>%  select(IdPanelist,Sex,AgeGroup) %>% distinct() %>%
                        tabyl(Sex,AgeGroup) %>% adorn_totals(c("row", "col")) %>%
                                                adorn_percentages("col") %>% 
                                                adorn_pct_formatting(rounding = "half up", digits = 0) %>%
                                                adorn_ns() %>%
                                                adorn_title("combined") %>%
                                                knitr::kable()
Sex/AgeGroup < 18 18 - 24 25 - 34 35 - 44 45 + NA_ Total
Female 58% (7) 70% (188) 73% (296) 70% (296) 50% (199) 75% (6) 65% (992)
Male 42% (5) 30% (82) 27% (111) 30% (129) 50% (202) 0% (0) 35% (529)
N.A. 0% (0) 0% (0) 0% (1) 0% (0) 0% (0) 25% (2) 0% (3)
Total 100% (12) 100% (270) 100% (408) 100% (425) 100% (401) 100% (8) 100% (1524)

En la base general tenemos un 65% de mujeres y un 35% de hombres mientras que en el subconjunto de usuarios con más de 100 visitas a un medio concreto a la misma hora de un mismo día, tenemos una mayor balanceo 54-46, respectivamente. Esto nos indica que, en términos relativos, hay mayor porcentaje de hombres entre los que más visitas a la hora hacen.

  1. ¿Cuáles son los medios con más engagement a nivel horario?

Contamos la frecuencia de aparición delos distintos medios en la muestra de altas repetición.

datosMedia_distinct %>% filter(visitasHora > 100) %>% group_by(Medio) %>% count()
## # A tibble: 5 x 2
## # Groups:   Medio [5]
##   Medio             n
##   <chr>         <int>
## 1 App_Instagram     1
## 2 Web_facebook     45
## 3 Web_instagram    19
## 4 Web_twitter       4
## 5 Web_youtube       3

Webs de Facebook e Instagram como clara ganadoras.

  1. ¿Cuales son los medios con mayor número de visitas los sábados en prime time?
datosMedia_distinct %>% filter(DiaSem == 'sábado', DayParting =='Primetime') %>% 
  group_by(Medio) %>% summarise(vSabadoPrime=sum(visitasHora)) %>% arrange(desc(vSabadoPrime))
## # A tibble: 49 x 2
##    Medio         vSabadoPrime
##    <chr>                <int>
##  1 App_Instagram         4926
##  2 Web_youtube           2810
##  3 App_Facebook          2219
##  4 Web_twitter           1589
##  5 Web_instagram         1401
##  6 Web_facebook          1210
##  7 App_Twitter            749
##  8 App_YouTube            742
##  9 App_TikTok             597
## 10 TV_Telecinco           492
## # ... with 39 more rows
  1. ¿Y considerando solo los medios de TV?
datosMedia_distinct %>% filter(DiaSem == 'sábado', DayParting =='Primetime', Plat=='TV') %>% 
  group_by(Medio) %>% summarise(vSabadoPrime=sum(visitasHora)) %>% arrange(desc(vSabadoPrime))
## # A tibble: 13 x 2
##    Medio         vSabadoPrime
##    <chr>                <int>
##  1 TV_Telecinco           492
##  2 TV_Antena3             407
##  3 TV_Cuatro              258
##  4 TV_LaSexta             232
##  5 TV_FDF                 172
##  6 TV_Neox                101
##  7 TV_Energy               89
##  8 TV_Mega                 67
##  9 TV_Nova                 59
## 10 TV_Boing                54
## 11 TV_Divinity             47
## 12 TV_Bmad                 20
## 13 TV_Atreseries            3

Con estas cosas de código y estructura mental podemos preguntarnos y, lo más importante, sabemos respondernos casi cualquier cuestión sobre la información contenida en el dataset. Resulta muy importante tener el conocimiento de los datos a nivel descriptivo para poder llegar a buenos modelos predictivos.

Mino estudio de la variable creada como potencial respuesta a predecir

datosMedia_distinct %>% plot_ly(x= ~visitasHora) %>% add_histogram()
datosMedia_distinct %>% plot_ly(x= ~visitasHora) %>% add_boxplot()

Distribución muy asimétrica. Ya veremos como tratar estas cosas. De momento, podemos pintar lo que esté por debajo de cierto umbral para poder ver algo de la distribución general.

datosMedia_distinct %>% filter(visitasHora <50) %>% plot_ly(x= ~visitasHora) %>% add_histogram()
datosMedia_distinct %>% filter(visitasHora <50) %>% plot_ly(x= ~visitasHora) %>% add_boxplot()

Está claro que el grueso de la distribución se concentra en valore menores que 5. Esto podría cambiar la perspectiva del modelado hacia una aproximación por clasificación o regresión ordinal…

Continuará…

La idea es que vayamos completando este documento (cada uno lo customiza a su gusto) y nos queda como una chuleta de códigos potencialmente interesantes para la exploración de los datos.